home *** CD-ROM | disk | FTP | other *** search
/ PC Media 23 / PC MEDIA CD23.iso / share / prog / newmat / newmatb.txt < prev    next >
Encoding:
Text File  |  1995-01-08  |  24.9 KB  |  604 lines

  1. //$$ newmatb.txt                                   Design
  2.  
  3.  
  4. Copyright (C) 1991,2,3,4: R B Davies
  5.  
  6. Please treat this as an academic publication. You can use the ideas but
  7. please acknowledge in any publication.
  8.  
  9. See also my publication in the proceedings of the second (1994)
  10. object-oriented numerics conference published by Rogue-wave Software.
  11.  
  12.  
  13.  
  14.                       Notes on the design of the package
  15.                       ==================================
  16.  
  17. Contents
  18. ========
  19.  
  20. What this is package for
  21. What size of matrices?
  22. Allow matrix expressions?
  23. Which matrix types?
  24. What element types?
  25. Naming convention
  26. Row and Column index ranges
  27. Structure of matrix objects
  28. Data storage - one block or several
  29. Data storage - by row or by column or other
  30. Storage of symmetric matrices
  31. Element access - method and checking
  32. Use iterators?
  33. Memory management - reference counting or status variable?
  34. Evaluation of expressions - use two stage method?
  35. How to overcome an explosion in number of operations
  36. Destruction of temporaries
  37. A calculus of matrix types
  38. Error handling
  39. Sparse matrices
  40. Complex matrices
  41.  
  42.  
  43. I describe some of the ideas behind this package, some of the decisions
  44. that I needed to make and give some details about the way it works. You
  45. don't need to read this file in order to use the package.
  46.  
  47. I don't think it is obvious what is the best way of going about
  48. structuring a matrix package. And I don't think you can figure this
  49. out with "thought" experiments. Different people have to try out
  50. different approaches. And someone else may have to figure out which is
  51. best. Or, more likely, the ultimate packages will lift some ideas from
  52. each of a variety of trial packages. So, I don't claim my package is an
  53. "ultimate" package, but simply a trial of a number of ideas.
  54.  
  55. But I do hope it will meet the immediate requirements of some people who
  56. need to carry out matrix calculations.
  57.  
  58.  
  59. What this is package for
  60. ------------------------
  61.  
  62. The package is used for the manipulation of matrices, including the
  63. standard operations such as multiplication as understood by numerical
  64. analysts, engineers and mathematicians. A matrix is a two dimensional
  65. array of numbers. However, very special operations such as matrix
  66. multiplication are defined specifically for matrices. This means that a
  67. "matrix" package tends to be different from a general "array" package.
  68.  
  69. I see a matrix package as providing the following
  70.  
  71. 1.   Evaluation of matrix expressions in a form familiar to
  72.      scientists and engineers. For example  A = B * (C + D.t());
  73. 2.   Access to the elements of a matrix;
  74. 3.   Access to submatrices;
  75. 4.   Common elementary matrix functions such as determinant and trace;
  76. 5.   A platform for developing advanced matrix functions such as eigen-
  77.      value analysis;
  78. 6.   Good efficiency and storage management;
  79. 7.   Graceful exit from errors.
  80.  
  81. It may also provide
  82.  
  83. 8.   A variety of types of elements (eg real and complex);
  84. 9.   A variety of types of matrices (eg rectangular and symmetric).
  85.  
  86. In contrast an array package should provide
  87.  
  88. 1'.  Arrays can be copied with a single instruction; may have some
  89.      arithmetic operations, say + - and scalar + - * /, it may provide
  90.      matrix multiplication as a function;
  91. 2'.  High speed access to elements directly and with iterators;
  92. 3'.  Access to subarrays and rows (and columns?);
  93. 6'.  Good efficiency and storage management;
  94. 7'.  Graceful exit from errors;
  95. 8'.  A variety of types of elements (eg real and complex);
  96. 9'.  One, two and three dimensional arrays, at least, with starting
  97.      points of the indices set by user; may have arrays with ragged
  98.      rows.
  99.  
  100. It may be possible to amalgamate these two sets of requirements to some
  101. extent. However my package is definitely oriented towards the first set.
  102.  
  103. Even within the bounds set by the first set of requirements there is a
  104. substantial opportunity for variation between what different matrix
  105. packages might provide.
  106.  
  107. It is not possible to build a matrix package that will meet everyone's
  108. requirements. In many cases if you put in one facility, you impose
  109. overheads on everyone using the package. This both is storage required
  110. for the program and in efficiency. Likewise a package that is optimised
  111. towards handling large matrices is likely to become less efficient for
  112. very small matrices where the administration time for the matrix may
  113. become significant compared with the time to carry out the operations.
  114.  
  115. It is better to provide a variety of packages (hopefully compatible) so
  116. that most users can find one that meets their requirements. This package
  117. is intended to be one of these packages; but not all of them.
  118.  
  119. Since my background is in statistical methods, this package is oriented
  120. towards the kinds things you need for statistical analyses.
  121.  
  122.  
  123. What size of matrices?
  124. ----------------------
  125.  
  126. A matrix package may target small matrices (say 3 x 3), or medium sized
  127. matrices, or very large matrices. A package targeting very small
  128. matrices will seek to minimise administration. A package for medium
  129. sized or very large matrices can spend more time on administration in
  130. order to conserve space or optimise the evaluation of expressions. A
  131. package for very large matrices will need to pay special attention to
  132. storage and numerical properties.
  133.  
  134. This package is designed for medium sized matrices. This means it is
  135. worth introducing some optimisations, but I don't have to worry about
  136. the 64K limit that some compilers impose.
  137.  
  138.  
  139. Allow matrix expressions?
  140. -------------------------
  141.  
  142. I want to be able to write matrix expressions the way I would on paper.
  143. So if I want to multiply two matrices and then add the transpose of a
  144. third one I can write something like
  145.  
  146.    X = A * B + C.t();
  147.  
  148. I want this expression to be evaluated with close to the same efficiency
  149. as a hand-coded version. This is not so much of a problem with
  150. expressions including a multiply since the multiply will dominate the
  151. time. However, it is not so easy to achieve with expressions with just +
  152. and - .
  153.  
  154. A second requirement is that temporary matrices generated during the
  155. evaluation of an expression are destroyed as quickly as possible.
  156.  
  157. A desirable feature is that a certain amount of "intelligence" be
  158. displayed in the evaluation of an expression. For example, in the
  159. expression
  160.  
  161.    X = A.i() * B;
  162.  
  163. where i() denotes inverse, it would be desirable if the inverse wasn't
  164. explicitly calculated.
  165.  
  166.  
  167. Which matrix types?
  168. -------------------
  169.  
  170. As well as the usual rectangular matrices, matrices occuring repeatedly
  171. in numerical calculations are upper and lower triangular matrices,
  172. symmetric matrices and diagonal matrices. This is particularly the case
  173. in calculations involving least squares and eigenvalue calculations. So
  174. as a first stage these were the types I decided to include.
  175.  
  176. It is also necessary to have types row vector and column vector. In a
  177. "matrix" package, in contrast to an "array" package, it is necessary to
  178. have both these types since they behave differently in matrix
  179. expressions. The vector types can be derived for the rectangular matrix
  180. type, so having them does not greatly increase the complexity of the
  181. package.
  182.  
  183. The problem with having several matrix types is the number of versions
  184. of the binary operators one needs. If one has 5 distinct matrix types
  185. then a simple package will need 25 versions of each of the binary
  186. operators. In fact, we can evade this problem, but at the cost of some
  187. complexity.
  188.  
  189.  
  190. What element types?
  191. -------------------
  192.  
  193. Ideally we would allow element types double, float, complex and int, at
  194. least. It would be reasonably easy, using templates or equivalent, to
  195. provide a package which could handle a variety of element types.
  196. However, as soon as one starts implementing the binary operators between
  197. matrices with different element types, again one gets an explosion in
  198. the number of operations one needs to consider. Hence I decided to
  199. implement only one element type. But the user can decide whether this is
  200. float or double. The package assumes elements are of type Real. The user
  201. typedefs Real to float or double.
  202.  
  203. In retrospect, it would not be too hard to include matrices with complex
  204. matrix type.
  205.  
  206. It might also be worth including symmetric and triangular matrices with
  207. extra precision elements (double or long double) to be used for storage
  208. only and with a minimum of operations defined. These would be used for
  209. accumulating the results of sums of squares and product matrices or
  210. multistage Householder triangularisations.
  211.  
  212.  
  213. Naming convention
  214. -----------------
  215.  
  216. How are classes and public member functions to be named? As a general
  217. rule I have spelt identifiers out in full with individual words being
  218. capitalised. For example "UpperTriangularMatrix". If you don't like this
  219. you can #define or typedef shorter names. This convention means you can
  220. select an abbreviation scheme that makes sense to you.
  221.  
  222. The convention causes problems for Glockenspiel C++ on a PC feeding into
  223. Microsoft C. The names Glockenspiel generates exceed the the 32
  224. characters recognised by Microsoft C and ambiguities result. So it is
  225. necessary to #define shorter names.
  226.  
  227. Exceptions to the general rule are the functions for transpose and
  228. inverse. To make matrix expressions more like the corresponding
  229. mathematical formulae, I have used the single letter abbreviations, t()
  230. and i() .
  231.  
  232.  
  233. Row and Column index ranges
  234. ---------------------------
  235.  
  236. In mathematical work matrix subscripts usually start at one. In C, array
  237. subscripts start at zero. In Fortran, they start at one. Possibilities
  238. for this package were to make them start at 0 or 1 or be arbitrary.
  239. Alternatively one could specify an "index set" for indexing the rows and
  240. columns of a matrix. One would be able to add or multiply matrices only
  241. if the appropriate row and column index sets were identical.
  242.  
  243. In fact, I adopted the simpler convention of making the rows and columns
  244. of a matrix be indexed by an integer starting at one, following the
  245. traditional convention. In an earlier version of the package I had them
  246. starting at zero, but even I was getting mixed up when trying to use
  247. this earlier package. So I reverted to the more usual notation.
  248.  
  249.  
  250. Structure of matrix objects
  251. ---------------------------
  252.  
  253. Each matrix object contains the basic information such as the number of
  254. rows and columns and a status variable plus a pointer to the data
  255. array which is on the heap.
  256.  
  257.  
  258. Data storage - one block or several
  259. -----------------------------------
  260.  
  261. In this package the elements of the matrix are stored as a single array.
  262. Alternatives would have been to store each row as a separate array or a
  263. set of adjacent rows as a separate array. The present solution
  264. simplifies the program but limits the size of matrices in systems that
  265. have a 64k byte (or other) limit on the size of arrays. The large arrays
  266. may also cause problems for memory management in smaller machines.
  267.  
  268.  
  269. Data storage - by row or by column or other
  270. -------------------------------------------
  271.  
  272. In Fortran two dimensional arrays are stored by column. In most other
  273. systems they are stored by row. I have followed this later convention.
  274. This makes it easier to interface with other packages written in C but
  275. harder to interface with those written in Fortran.
  276.  
  277. This may have been a wrong decision. Most work on the efficient
  278. manipulation of large matrices is being done in Fortran. It would have
  279. been easier to use this work if I had adopted the Fortan convention.
  280.  
  281. An alternative would be to store the elements by mid-sized rectangular
  282. blocks. This might impose less strain on memory management when one
  283. needs to access both rows and columns.
  284.  
  285.  
  286. Storage of symmetric matrices
  287. -----------------------------
  288.  
  289. Symmetric matrices are stored as lower triangular matrices. The decision
  290. was pretty arbitrary, but it does slightly simplify the Cholesky
  291. decomposition program.
  292.  
  293.  
  294. Element access - method and checking
  295. ------------------------------------
  296.  
  297. We want to be able to use the notation A(i,j) to specify the (i,j)-th
  298. element of a matrix. This is the way mathematicians expect to address
  299. the elements of matrices. I consider the notation A[i][j] totally alien.
  300. However I may include it in a later version to help people converting
  301. from C. There are two ways of working out the address of A(i,j). One is
  302. using a "dope" vector which contains the first address of each row.
  303. Alternatively you can calculate the address using the formula
  304. appropriate for the structure of A. I use this second approach. It is
  305. probably slower, but saves worrying about an extra bit of storage. The
  306. other question is whether to check for i and j being in range. I do
  307. carry out this check following years of experience with both systems
  308. that do and systems that don't do this check.
  309.  
  310. I would hope that the routines I supply with this package will reduce
  311. your need to access elements of matrices so speed of access is not a
  312. high priority.
  313.  
  314.  
  315. Use iterators?
  316. --------------
  317.  
  318. Iterators are an alternative way of providing fast access to the
  319. elements of an array or matrix when they are to be accessed
  320. sequentially. They need to be customised for each type of matrix. I have
  321. not implemented iterators in this package, although some iterator like
  322. functions are used for some row and column functions.
  323.  
  324.  
  325. Memory management - reference counting or status variable?
  326. ----------------------------------------------------------
  327.  
  328. Consider the instruction
  329.  
  330.    X = A + B + C;
  331.  
  332. To evaluate this a simple program will add A to B putting the total in a
  333. temporary T1. Then it will add T1 to C creating another temporary T2
  334. which will be copied into X. T1 and T2 will sit around till the end of
  335. the block. It would be faster if the program recognised that T1 was
  336. temporary and stored the sum of T1 and C back into T1 instead of
  337. creating T2 and then avoided the final copy by just assigning the
  338. contents of T1 to X rather than copying. In this case there will be no
  339. temporaries requiring deletion. (More precisely there will be a header
  340. to be deleted but no contents).
  341.  
  342. For an instruction like
  343.  
  344.    X = (A * B) + (C * D);
  345.  
  346. we can't easily avoid one temporary being left over, so we would like
  347. this temporary deleted as quickly as possible.
  348.  
  349. I provide the functionality for doing this by attaching a status
  350. variable to each matrix. This indicates if the matrix is temporary so
  351. that its memory is available for recycling or deleting. Any matrix
  352. operation checks the status variables of the matrices it is working with
  353. and recycles or deletes any temporary memory.
  354.  
  355. An alternative or additional approach would be to use delayed copying.
  356. If a program requests a matrix to be copied, the copy is delayed until
  357. an instruction is executed which modifies the memory of either the
  358. original matrix or the copy. This saves the unnecessary copying in the
  359. previous examples. However, it does not provide the additional
  360. functionality of my approach.
  361.  
  362. It would be possible to have both delayed copy and tagging temporaries,
  363. but this seemed an unnecessary complexity. In particular delayed copy
  364. mechanisms seem to require two calls to the heap manager, using extra
  365. time and making building a memory compacting mechanism more difficult.
  366.  
  367.  
  368. Evaluation of expressions - use two stage method?
  369. -------------------------------------------------
  370.  
  371. Consider the instruction
  372.  
  373.    X = B - X;
  374.  
  375. The simple program will subtract X from B, store the result in a
  376. temporary T1 and copy T1 into X. It would be faster if the program
  377. recognised that the result could be stored directly into X. This would
  378. happen automatically if the program could look at the instruction first
  379. and mark X as temporary.
  380.  
  381. C programmers would expect to avoid the same problem with
  382.  
  383.    X = X - B;
  384.  
  385. by using an operator -= (which I haven't provided, yet)
  386.  
  387.    X -= B;
  388.  
  389. However this is an unnatural notation for non C users and it is much
  390. nicer to write
  391.  
  392.    X = X - B;
  393.  
  394. and know that the program will carry out the simplification.
  395.  
  396. Another example where this intelligent analysis of an instruction is
  397. helpful is in
  398.  
  399.    X = A.i() * B;
  400.  
  401. where i() denotes inverse. Numerical analysts know it is inefficient to
  402. evaluate this expression by carrying out the inverse operation and then
  403. the multiply. Yet it is a convenient way of writing the instruction. It
  404. would be helpful if the program recognised this expression and carried
  405. out the more appropriate approach.
  406.  
  407. To carry out this "intelligent" analysis of an instruction  matrix
  408. expressions are evaluated in two stages. In the the first stage a tree
  409. representation of the expression is formed.
  410.  
  411. For example (A+B)*C is represented by a tree
  412.  
  413.                     *
  414.                    / \
  415.                   +   C
  416.                  / \
  417.                 A   B
  418.  
  419. Rather than adding A and B the + operator yields an object of a class
  420. "AddedMatrix" which is just a pair of pointers to A and B. Then the *
  421. operator yields a "MultipliedMatrix" which is a pair of pointers to the
  422. "AddedMatrix" and C. The tree is examined for any simplifications and
  423. then evaluated recursively.
  424.  
  425. Further possibilities not yet included are to recognise A.t()*A and
  426. A.t()+A as symmetric or to improve the efficiency of evaluation of
  427. expressions like A+B+C, A*B*C, A*B.t()  [t() denotes transpose].
  428.  
  429. One of the disadvantages of the two-stage approach is that the types of
  430. matrix expressions are determined at run-time. So the compiler will not
  431. detect errors of the type
  432.  
  433.    Matrix M; DiagonalMatrix D; ....; D = M;
  434.  
  435. We don't allow conversions using = when information would be lost. Such
  436. errors will be detected when the statement is executed.
  437.  
  438.  
  439. How to overcome an explosion in number of operations
  440. ----------------------------------------------------
  441.  
  442. The package attempts to solve the problem of the large number of
  443. versions of the binary operations required when one has a variety of
  444. types. With n types of matrices the binary operations will each require
  445. n-squared separate algorithms. Some reduction in the number may be
  446. possible by carrying out conversions. However the situation rapidly
  447. becomes impossible with more than 4 or 5 types.
  448.  
  449. Doug Lea told me that it was possible to avoid this problem. I don't
  450. know what his solution is. Here's mine.
  451.  
  452. Each matrix type includes routines for extracting individual rows or
  453. columns. I assume a row or column consists of a sequence of zeros, a
  454. sequence of stored values and then another sequence of zeros. Only a
  455. single algorithm is then required for each binary operation. The rows
  456. can be located very quickly since most of the matrices are stored row by
  457. row. Columns must be copied and so the access is somewhat slower. As far
  458. as possible my algorithms access the matrices by row.
  459.  
  460. An alternative approach of using iterators will be slower since the
  461. iterators will involve a virtual function access at each step.
  462.  
  463. There is another approach. Each of the matrix types defined in this
  464. package can be set up so both rows and columns have their elements at
  465. equal intervals provided we are prepared to store the rows and columns
  466. in up to three chunks. With such an approach one could write a single
  467. "generic" algorithm for each of multiply and add. This would be a
  468. reasonable alternative to my approach.
  469.  
  470. I provide several algorithms for operations like + . If one is adding
  471. two matrices of the same type then there is no need to access the
  472. individual rows or columns and a faster general algorithm is
  473. appropriate.
  474.  
  475. Generally the method works well. However symmetric matrices are not
  476. always handled very efficiently (yet) since complete rows are not stored
  477. explicitly.
  478.  
  479. The original version of the package did not use this access by row or
  480. column method and provided the multitude of algorithms for the
  481. combination of different matrix types. The code file length turned out
  482. to be just a little longer than the present one when providing the same
  483. facilities with 5 distinct types of matrices. It would have been very
  484. difficult to increase the number of matrix types in the original
  485. version. Apparently 4 to 5 types is about the break even point for
  486. switching to the approach adopted in the present package.
  487.  
  488. However it must also be admitted that there is a substantial overhead in
  489. the approach adopted in the present package for small matrices. The test
  490. program developed for the original version of the package takes 30 to
  491. 50% longer to run with the current version. This is for matrices in the
  492. range 6x6 to 10x10.
  493.  
  494. To try to improve the situation a little I do provide an ordinary matrix
  495. multiplication routine for the case when all the matrices involved are
  496. rectangular.
  497.  
  498.  
  499. Destruction of temporaries
  500. --------------------------
  501.  
  502. Versions before version 5 of newmat did not work correctly with Gnu C++.
  503. This was because the tree structure used to represent a matrix
  504. expression was set up on the stack. This was fine for AT&T, Borland and
  505. Zortech C++. However Gnu C++ destroys temporary structures as soon as
  506. the function that accesses them finishes. The other compilers wait until
  507. the end of the current expression or current block. To overcome this
  508. problem, there is now an option to store the temporaries forming the
  509. tree structure on the heap (created with new) and to delete them
  510. explicitly. Activate the definition of TEMPS_DESTROYED_QUICKLY to set
  511. this option. In fact, I suggest this be the default option as, with it,
  512. the package uses less storage and runs faster.
  513.  
  514. There still exist situations Gnu C++ will go wrong. These include
  515. statements like
  516.  
  517. A = X * Matrix(P * Q); Real r = (A*B)(3,4);
  518.  
  519. Neither of these kinds of statements will occur often in practice.
  520.  
  521.  
  522. A calculus of matrix types
  523. --------------------------
  524.  
  525. The program needs to be able to work out the class of the result of a
  526. matrix expression. This is to check that a conversion is legal or to
  527. determine the class of a temporary. To assist with this, a class
  528. MatrixType is defined. Operators +, -, *, >= are defined to calculate
  529. the types of the results of expressions or to check that conversions are
  530. legal.
  531.  
  532.  
  533. Error handling
  534. --------------
  535.  
  536. The package now does have a moderately graceful exit from errors.
  537. Originally I thought I would wait until exceptions became available in
  538. C++. Trying to set up an error handling scheme that did not involve an
  539. exception-like facility was just too complicated. Version 5 of this
  540. package included the beginnings of such an approach. The approach in the
  541. present package, attempting to implement C++ exceptions, is not
  542. completely satisfactory, but seems a good interim solution.
  543.  
  544. The exception mechanism cannot clean-up objects explicitly created by
  545. new. This must be explicitly carried out by the package writer or the
  546. package user. I have not yet done this with the present package so
  547. occasionally a little garbage may be left behind after an exception. I
  548. don't think this is a big problem, but it is one that needs fixing.
  549.  
  550. I identify five categories of errors:
  551.  
  552.    Programming error - eg illegal conversion of a matrix, subscript out
  553.    of bounds, matrix dimensions don't match;
  554.  
  555.    Illegal data error - eg Cholesky of a non-positive definite matrix;
  556.  
  557.    Out of space error - "new" returns a null pointer;
  558.  
  559.    Convergence failure - an iterative operation fails to converge;
  560.  
  561.    Internal error - failure of an internal check.
  562.  
  563. Some of these have subcategories, which are nicely represented by
  564. derived exception classes. But I don't think it is a good idea for
  565. package users to refer to these as they may change in the next release
  566. of the package.
  567.  
  568.  
  569. Sparse matrices
  570. ---------------
  571.  
  572. The package does not yet support sparse matrices.
  573.  
  574. For sparse matrices there is going to be some kind of structure vector.
  575. It is going to have to be calculated for the results of expressions in
  576. much the same way that types are calculated. In addition, a whole new
  577. set of row and column operations would have to be written.
  578.  
  579. Sparse matrices are important for people solving large sets of
  580. differential equations as well as being important for statistical and
  581. operational research applications. I think comprehensive matrix package
  582. needs to implement sparse matrices.
  583.  
  584.  
  585. Complex matrices
  586. ----------------
  587.  
  588. The package does not yet support matrices with complex elements. There
  589. are at least two approaches to including these. One is to have matrices
  590. with complex elements. This probably means making new versions of the
  591. basic row and column operations for Real*Complex, Complex*Complex,
  592. Complex*Real and similarly for + and -. This would be OK, except that I
  593. am also going to have to also do this for sparse and when you put these
  594. together, the whole thing will get out of hand.
  595.  
  596. The alternative is to represent a Complex matrix by a pair of Real
  597. matrices. One probably needs another level of decoding expressions but I
  598. think it might still be simpler than the first approach. There is going
  599. to be a problem with accessing elements and the final package may be a
  600. little less efficient that one using the previous representation.
  601.  
  602. Complex matrices are used extensively by electrical engineers and really
  603. should be fully supported in a comprehensive package.
  604.